package club.xyes.zkh.ecommerce.service.impl;

import club.xyes.zkh.ecommerce.commons.dto.Tuple2;
import club.xyes.zkh.ecommerce.commons.entity.Coupon;
import club.xyes.zkh.ecommerce.commons.entity.Shop;
import club.xyes.zkh.ecommerce.commons.entity.User;
import club.xyes.zkh.ecommerce.commons.entity.UserCoupon;
import club.xyes.zkh.ecommerce.commons.exception.BadRequestException;
import club.xyes.zkh.ecommerce.commons.exception.DiscountException;
import club.xyes.zkh.ecommerce.commons.exception.ResourceNotFoundException;
import club.xyes.zkh.ecommerce.commons.util.CouponUtils;
import club.xyes.zkh.ecommerce.commons.util.DateTimeUtil;
import club.xyes.zkh.ecommerce.commons.util.DiscountUtil;
import club.xyes.zkh.ecommerce.commons.util.TextUtil;
import club.xyes.zkh.ecommerce.dao.mapper.UserCouponMapper;
import club.xyes.zkh.ecommerce.service.UserCouponService;
import club.xyes.zkh.ecommerce.service.listener.OnCouponGrantListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * Create by 郭文梁 2019/4/18 0018 13:42
 * UserCouponServiceImpl
 * 用户优惠券关系业务行为实现
 *
 * @author 郭文梁
 * @data 2019/4/18 0018
 */
@Service
@Slf4j
public class UserCouponServiceImpl extends BaseServiceImpl<UserCoupon> implements UserCouponService {
    private final UserCouponMapper mapper;

    @Autowired
    public UserCouponServiceImpl(UserCouponMapper mapper) {
        super(mapper);
        this.mapper = mapper;
    }

    @Override
    public List<UserCoupon> findByUserIdFetchAll(Integer userId) {
        return mapper.selectByUserIdFetchAll(userId);
    }

    @Override
    public Tuple2<UserCoupon, Integer> getDiscount(Integer id, User user, Shop shop, Integer fee) throws DiscountException {
        if (id == null) {
            return Tuple2.of(null, null);
        }
        UserCoupon userCoupon = getFetchAllRequired(id);
        Coupon coupon = userCoupon.getCoupon();
        checkAvailable(userCoupon, user, shop, fee, false);
        Integer discountWay = coupon.getDiscountWay();
        int discount;
        if (discountWay == Coupon.DISCOUNT_CASH_COUPON) {
            discount = Integer.parseInt(coupon.getAmount());
        } else if (discountWay == Coupon.DISCOUNT_DISCOUNT_COUPON) {
            discount = fee - BigDecimal.valueOf(fee).multiply(new BigDecimal(coupon.getDiscountRate())).intValue();
        } else {
            throw new IllegalStateException("Unexpected discount way " + discountWay);
        }
        return Tuple2.of(userCoupon, discount);
    }

    @Override
    public UserCoupon getFetchAll(Integer id) {
        return mapper.selectByIdFetchAll(id);
    }

    @Override
    public UserCoupon getFetchAllRequired(Integer id) {
        UserCoupon userCoupon = getFetchAll(id);
        if (userCoupon == null) {
            throw new ResourceNotFoundException(UserCoupon.class, id);
        }
        return userCoupon;
    }

    @Override
    public void setStatus(UserCoupon userCoupon, int status) {
        userCoupon.setStatus(status);
        updateById(userCoupon);
    }

    @Override
    public List<UserCoupon> available(User user, Shop shop, int fee, boolean useMembership) {
        List<UserCoupon> coupons = findByUserAndShop(user, shop);
        return coupons.stream().filter(v -> {
            try {
                checkAvailable(v, user, shop, fee, useMembership);
                return true;
            } catch (DiscountException e) {
                return false;
            }
        }).collect(Collectors.toList());
    }

    @Override
    public List<UserCoupon> findByUserAndShop(User user, Shop shop) {
        return mapper.selectEnableByUserIdAndShopId(user.getId(), shop.getId());
    }

    @Override
    public UserCoupon take(User user, Coupon coupon) {
        if (existsByUserAndCoupon(user, coupon)) {
            throw new BadRequestException("您已领取过该优惠券");
        }
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setCoupon(coupon);
        userCoupon.setCouponId(coupon.getId());
        userCoupon.setUser(user);
        userCoupon.setUserId(user.getId());
        userCoupon.setStatus(UserCoupon.STATUS_ENABLE);
        return save(userCoupon);
    }

    @Override
    public List<UserCoupon> autoGrant(User user,
                                      Shop shop,
                                      List<Coupon> matchedCoupons,
                                      int amount,
                                      OnCouponGrantListener listener) {
        List<UserCoupon> owned = findByUserAndShop(user, shop);
        List<UserCoupon> userCoupons = matchedCoupons
                .stream()
                .filter(coupon -> {
                    for (UserCoupon ownedCoupon : owned) {
                        if (Objects.equals(ownedCoupon.getCoupon().getId(), coupon.getId())) {
                            return false;
                        }
                    }
                    return true;
                })
                .filter(CouponUtils::checkAvailable)
                .map(coupon -> {
                    UserCoupon userCoupon = new UserCoupon();
                    userCoupon.setCouponId(coupon.getId());
                    userCoupon.setCoupon(coupon);
                    userCoupon.setUserId(user.getId());
                    userCoupon.setUser(user);
                    userCoupon.setStatus(UserCoupon.STATUS_ENABLE);
                    return userCoupon;
                }).collect(Collectors.toList());
        List<UserCoupon> userCouponList = save(userCoupons);
        if (userCouponList != null) {
            userCouponList.forEach(uc -> listener.onGrant(user, uc.getCoupon(), shop));
        }
        return userCouponList;
    }

    @Override
    public int countByUserAndShop(Integer userId, Integer shopId) {
        return mapper.selectCountByShopIdAndUserId(shopId, userId);
    }

    @Override
    public List<UserCoupon> grant(Coupon coupon,
                                  Shop shop,
                                  List<Integer> userIdList,
                                  List<User> users,
                                  OnCouponGrantListener listener) {
        List<UserCoupon> ucs = userIdList.stream().map(id -> {
            UserCoupon uc = new UserCoupon();
            uc.setUserId(id);
            uc.setCouponId(coupon.getId());
            uc.setStatus(UserCoupon.STATUS_ENABLE);
            return uc;
        }).peek(uc -> {
            User user = findUser(users, uc.getUserId());
            if (user == null) {
                throw new BadRequestException("用户不存在:" + uc.getUserId());
            }
            uc.setCoupon(coupon);
            uc.setUser(user);
        }).collect(Collectors.toList());
        List<UserCoupon> userCouponList = save(ucs);
        userCouponList.forEach(uc -> listener.onGrant(uc.getUser(), coupon, shop));
        return userCouponList;
    }

    private User findUser(List<User> users, Integer id) {
        for (User u : users) {
            if (Objects.equals(u.getId(), id)) {
                return u;
            }
        }
        return null;
    }

    private boolean existsByUserAndCoupon(User user, Coupon coupon) {
        UserCoupon query = new UserCoupon();
        query.setUserId(user.getId());
        query.setCouponId(coupon.getId());
        return count(query) > 0;
    }

    /**
     * 检查优惠券是否可用
     *
     * @param userCoupon 优惠券
     * @param user       用户
     * @param shop       商铺
     * @param fee        应付金额
     * @throws DiscountException 优惠信息异常
     */
    private void checkAvailable(UserCoupon userCoupon, User user, Shop shop, Integer fee, boolean useMembershipDiscount) throws DiscountException {
        Coupon coupon = userCoupon.getCoupon();
        if (!Objects.equals(userCoupon.getUserId(), user.getId())) {
            //该优惠券不属于该用户
            throw new DiscountException(String.format(
                    "The userId of UserCoupon is [%s], but your id is [%s]",
                    userCoupon.getUserId(),
                    user.getId()
            ));
        }
        if (!Objects.equals(coupon.getShopId(), shop.getId())) {
            //该优惠券不是当前消费商铺所发
            throw new DiscountException(String.format(
                    "The shopId of UserCoupon is [%s], but current shop id is [%s]",
                    coupon.getShop(),
                    shop.getId()
            ));
        }
        if (useMembershipDiscount && coupon.getMutualExclusionWithMembershipDiscount()) {
            //不能同时用户会员优惠和优惠券优惠
            throw new DiscountException("Can't use coupons and membership at the same time!");
        }
        if (TextUtil.isTrimedNotEmpty(coupon.getMinAmount()) && Integer.parseInt(coupon.getMinAmount()) >= fee) {
            //消费金额未达到条件
            throw new DiscountException("Consumption amount does not reach the minimum consumption amount");
        }
        if (userCoupon.getStatus() != UserCoupon.STATUS_ENABLE) {
            //优惠券状态无效
            throw new DiscountException("Coupon status" + userCoupon.getStatus() + " is invalid");
        }
        if (!DiscountUtil.isCouponInTime(coupon, DateTimeUtil.now())) {
            //不在使用时间区间内
            throw new DiscountException("Out of use time interval");
        }
    }
}
